package com.example.mq1.mqclient;

import com.example.mq1.common.BinaryTool;
import com.example.mq1.common.Consumer;
import com.example.mq1.common.MqException;
import com.example.mq1.mqserver.core.BasicProperties;
import com.example.mq1.mqserver.core.ExchangeType;
import com.example.mq1.network.*;
import com.sun.corba.se.spi.ior.ObjectKey;
import com.sun.xml.internal.ws.config.management.policy.ManagementPolicyValidator;
import org.apache.ibatis.annotations.Param;
import org.apache.logging.log4j.message.StringFormattedMessage;
import sun.security.x509.IPAddressName;

import javax.print.attribute.HashPrintRequestAttributeSet;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

//public class Channel {
//
//    private String channelId;
//    //记录当前 channel 是属于哪一个连接的
//    private Connection connection;
//    //记录服务器传入的响应, key 是 rid
//    private ConcurrentHashMap<String, BasicResponse> responseMap = new ConcurrentHashMap<>();
//    //约定一个 channel 只能维护一个 回调
//    private Consumer consumer;
//
//    public Channel(String channelId, Connection connection) {
//        this.channelId = channelId;
//        this.connection = connection;
//    }
//
//    public String getChannelId() {
//        return channelId;
//    }
//
//    public void setChannelId(String channelId) {
//        this.channelId = channelId;
//    }
//
//    public Connection getConnection() {
//        return connection;
//    }
//
//    public void setConnection(Connection connection) {
//        this.connection = connection;
//    }
//
//    public ConcurrentHashMap<String, BasicResponse> getResponseMap() {
//        return responseMap;
//    }
//
//    public void setResponseMap(ConcurrentHashMap<String, BasicResponse> responseMap) {
//        this.responseMap = responseMap;
//    }
//
//    public Consumer getConsumer() {
//        return consumer;
//    }
//
//    public void setConsumer(Consumer consumer) {
//        this.consumer = consumer;
//    }
//
//    /**
//     * 0x1
//     * 创建 channel
//     * @return
//     * @throws IOException
//     */
//    public boolean createChannel() throws IOException {
//        //1.构造 payload
//        BasicRequest basicRequest = new BasicRequest();
//        basicRequest.setRid(getRidUUID());
//        basicRequest.setChannelId(channelId);
//        byte[] payload = BinaryTool.toBytes(basicRequest);
//        //2.构造发送请求
//        Request request = new Request();
//        request.setType(0x1);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//        connection.writeRequest(request);
//        //3.阻塞等待服务器响应
//        BasicResponse basicResponse = waitResponse(basicRequest.getRid());
//        return basicResponse.isOk();
//    }
//
//    private BasicResponse waitResponse(String rid) {
//        BasicResponse basicResponse = null;
//        while((basicResponse = responseMap.get(rid)) == null) {
//            synchronized (this) {
//                try {
//                    wait();
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }
//            }
//        }
//        responseMap.remove(rid);
//        return basicResponse;
//    }
//
//    public void putReturn(BasicResponse basicResponse) {
//        responseMap.put(basicResponse.getRid(), basicResponse);
//        synchronized (this) {
//            notifyAll();
//        }
//    }
//
//    private String getRidUUID() {
//        return "R-" + UUID.randomUUID();
//    }
//
//    /**
//     * 0x2
//     * 关闭 channel
//     * @return
//     * @throws IOException
//     */
//    public boolean close() throws IOException {
//        BasicRequest basicRequest = new BasicRequest();
//        basicRequest.setRid(getRidUUID());
//        basicRequest.setChannelId(channelId);
//        byte[] payload = BinaryTool.toBytes(basicRequest);
//        Request request = new Request();
//        request.setType(0x2);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse basicResponse = waitResponse(basicRequest.getRid());
//        return basicResponse.isOk();
//    }
//
//    /**
//     * 0x3
//     * @param exchangeName
//     * @param exchangeType
//     * @param durable
//     * @param autoDelete
//     * @param arguments
//     * @return
//     */
//    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable,
//                                   boolean autoDelete, Map<String, Object> arguments) throws IOException {
//        ExchangeDeclareArguments exchangeDeclareArguments = new ExchangeDeclareArguments();
//        exchangeDeclareArguments.setRid(getRidUUID());
//        exchangeDeclareArguments.setChannelId(channelId);
//        exchangeDeclareArguments.setExchangeName(exchangeName);
//        exchangeDeclareArguments.setExchangeType(exchangeType);
//        exchangeDeclareArguments.setDurable(durable);
//        exchangeDeclareArguments.setAutoDelete(autoDelete);
//        exchangeDeclareArguments.setArguments(arguments);
//        byte[] payload = BinaryTool.toBytes(exchangeDeclareArguments);
//
//        Request request = new Request();
//        request.setType(0x3);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse basicResponse = waitResponse(exchangeDeclareArguments.getRid());
//        return basicResponse.isOk();
//    }
//
//    /**
//     * 0x4
//     * @param exchangeName
//     * @return
//     */
//    public boolean exchangeDelete(String exchangeName) throws IOException {
//        ExchangeDeleteArguments exchangeDeleteArguments = new ExchangeDeleteArguments();
//        exchangeDeleteArguments.setRid(getRidUUID());
//        exchangeDeleteArguments.setChannelId(channelId);
//        exchangeDeleteArguments.setExchangeName(exchangeName);
//        byte[] payload = BinaryTool.toBytes(exchangeDeleteArguments);
//
//        Request request = new Request();
//        request.setType(0x4);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(exchangeDeleteArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0x5
//     * @param queueName
//     * @param durable
//     * @param exclusive
//     * @param autoDelete
//     * @param arguments
//     * @return
//     */
//    public boolean queueDeclare(String queueName, boolean durable, boolean exclusive, boolean autoDelete,
//                                Map<String, Object> arguments) throws IOException {
//        QueueDeclareArguments queueDeclareArguments = new QueueDeclareArguments();
//        queueDeclareArguments.setRid(getRidUUID());
//        queueDeclareArguments.setChannelId(channelId);
//        queueDeclareArguments.setQueueName(queueName);
//        queueDeclareArguments.setDurable(durable);
//        queueDeclareArguments.setExclusive(exclusive);
//        queueDeclareArguments.setArguments(arguments);
//        byte[] payload = BinaryTool.toBytes(queueDeclareArguments);
//
//        Request request = new Request();
//        request.setType(0x5);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(queueDeclareArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0x6
//     * @param queueName
//     * @return
//     * @throws IOException
//     */
//    public boolean queueDelete(String queueName) throws IOException {
//        QueueDeleteArguments queueDeleteArguments = new QueueDeleteArguments();
//        queueDeleteArguments.setRid(getRidUUID());
//        queueDeleteArguments.setChannelId(channelId);
//        queueDeleteArguments.setQueueName(queueName);
//        byte[] payload = BinaryTool.toBytes(queueDeleteArguments);
//
//        Request request = new Request();
//        request.setType(0x6);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(queueDeleteArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0x7
//     * @param exchangeName
//     * @param queueName
//     * @param bindingKey
//     * @return
//     * @throws IOException
//     */
//    public boolean queueBind(String exchangeName, String queueName, String bindingKey) throws IOException {
//        QueueBindArguments queueBindArguments = new QueueBindArguments();
//        queueBindArguments.setRid(getRidUUID());
//        queueBindArguments.setChannelId(channelId);
//        queueBindArguments.setExchangeName(exchangeName);
//        queueBindArguments.setQueueName(queueName);
//        queueBindArguments.setBindingKey(bindingKey);
//        byte[] payload = BinaryTool.toBytes(queueBindArguments);
//
//        Request request = new Request();
//        request.setType(0x7);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(queueBindArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0x8
//     * @param exchangeName
//     * @param queueName
//     * @return
//     */
//    public boolean queueUnBind(String exchangeName, String queueName) throws IOException {
//        QueueUnBindArguments queueUnBindArguments = new QueueUnBindArguments();
//        queueUnBindArguments.setRid(getRidUUID());
//        queueUnBindArguments.setChannelId(channelId);
//        queueUnBindArguments.setExchangeName(exchangeName);
//        queueUnBindArguments.setQueueName(queueName);
//        byte[] payload = BinaryTool.toBytes(queueUnBindArguments);
//
//        Request request = new Request();
//        request.setType(0x8);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(queueUnBindArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0x9
//     * @param exchangeName
//     * @param routingKey
//     * @param basicProperties
//     * @param body
//     * @return
//     */
//    public boolean basicPublish(String exchangeName, String routingKey,
//                                BasicProperties basicProperties, byte[] body) throws IOException {
//        BasicPublishArguments basicPublishArguments = new BasicPublishArguments();
//        basicPublishArguments.setRid(getRidUUID());
//        basicPublishArguments.setChannelId(channelId);
//        basicPublishArguments.setExchangeName(exchangeName);
//        basicPublishArguments.setRoutingKey(routingKey);
//        basicPublishArguments.setBasicProperties(basicProperties);
//        basicPublishArguments.setBody(body);
//        byte[] payload = BinaryTool.toBytes(basicPublishArguments);
//
//        Request request = new Request();
//        request.setType(0x9);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(basicPublishArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0xa
//     * @param consumerTag
//     * @param queueName
//     * @param autoAck
//     * @return
//     * @throws IOException
//     */
//    public boolean basicConsume(String consumerTag, String queueName, boolean autoAck, Consumer consumer) throws IOException, MqException {
//        if(this.consumer != null) {
//            throw new MqException("[Channel] 该 channel 已经设置过 consumer！");
//        }
//        this.consumer = consumer;
//        BasicConsumeArguments basicConsumeArguments = new BasicConsumeArguments();
//        basicConsumeArguments.setRid(getRidUUID());
//        basicConsumeArguments.setChannelId(channelId);
//        basicConsumeArguments.setConsumerTag(channelId); //此处 consumerTag 也是使用 channelId 来表示
//        basicConsumeArguments.setQueueName(queueName);
//        basicConsumeArguments.setAutoAck(autoAck);
//        byte[] payload = BinaryTool.toBytes(basicConsumeArguments);
//
//        Request request = new Request();
//        request.setType(0xa);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(basicConsumeArguments.getRid());
//        return response.isOk();
//    }
//
//    /**
//     * 0xb
//     * @param queueName
//     * @param messageId
//     * @return
//     * @throws IOException
//     */
//    public boolean basicAck(String queueName, String messageId) throws IOException {
//        BasicAckArguments basicAckArguments = new BasicAckArguments();
//        basicAckArguments.setRid(getRidUUID());
//        basicAckArguments.setChannelId(channelId);
//        basicAckArguments.setQueueName(queueName);
//        basicAckArguments.setMessageId(messageId);
//        byte[] payload = BinaryTool.toBytes(basicAckArguments);
//
//        Request request = new Request();
//        request.setType(0xb);
//        request.setLength(payload.length);
//        request.setPayload(payload);
//
//        connection.writeRequest(request);
//        BasicResponse response = waitResponse(basicAckArguments.getRid());
//        return response.isOk();
//    }
//
//}

public class Channel {

    //channel 唯一身份标识
    private String channelId;
    //记录当前 channel 是属于哪一个 connection
    private Connection connection = null;
    //记录服务器来的响应，key 是 rid
    private ConcurrentHashMap<String, BasicResponse> responseMap = new ConcurrentHashMap<>();
    //约定一个 channel 记录一个 Consumer
    private Consumer consumer;

    public Channel(String channelId, Connection connection) {
        this.channelId = channelId;
        this.connection = connection;
    }

    public String getChannelId() {
        return channelId;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public Connection getConnection() {
        return connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public ConcurrentHashMap<String, BasicResponse> getResponseMap() {
        return responseMap;
    }

    public void setResponseMap(ConcurrentHashMap<String, BasicResponse> responseMap) {
        this.responseMap = responseMap;
    }

    public Consumer getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer consumer) {
        this.consumer = consumer;
    }

    /**
     * 放入响应对象到 channel 的 map 中，然后唤醒所有线程
     * @param rid
     * @param basicResponse
     */
    public void handlerResponse(String rid, BasicResponse basicResponse) {
        synchronized (this) {
            responseMap.put(rid, basicResponse);
            notifyAll();
        }
    }

    /**
     * 0x1
     */
    public boolean createChannel() throws IOException {
        BasicRequest basicRequest = new BasicRequest();
        basicRequest.setRid(generateRid());
        basicRequest.setChannelId(channelId);
        byte[] payload = BinaryTool.toBytes(basicRequest);

        Request request = new Request();
        request.setType(0x1);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(basicRequest.getRid());
        return basicResponse.isOk();
    }

    /**
     * 等待响应
     * @param rid
     * @return
     */
    private BasicResponse waitResponse(String rid) {
        BasicResponse basicResponse = null;
        synchronized (this) {
            while((basicResponse = responseMap.get(rid)) == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return basicResponse;
    }

    /**
     * 生成一个 rid
     * @return
     */
    private String generateRid() {
        return "R-" + UUID.randomUUID().toString();
    }

    /**
     * 0x2
     */
    public boolean close() throws IOException {
        BasicRequest basicRequest = new BasicRequest();
        basicRequest.setRid(generateRid());
        basicRequest.setChannelId(channelId);
        byte[] payload = BinaryTool.toBytes(basicRequest);

        Request request = new Request();
        request.setType(0x2);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(basicRequest.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x3
     * @param exchangeName
     * @param exchangeType
     * @param durable
     * @param autoDelete
     * @param arguments
     * @return
     * @throws IOException
     */
    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable, boolean autoDelete,
                                   Map<String, Object> arguments) throws IOException {
        ExchangeDeclareArguments exchangeDeclareArguments = new ExchangeDeclareArguments();
        exchangeDeclareArguments.setRid(generateRid());
        exchangeDeclareArguments.setChannelId(channelId);
        exchangeDeclareArguments.setExchangeName(exchangeName);
        exchangeDeclareArguments.setExchangeType(exchangeType);
        exchangeDeclareArguments.setDurable(durable);
        exchangeDeclareArguments.setAutoDelete(autoDelete);
        exchangeDeclareArguments.setArguments(arguments);
        byte[] payload = BinaryTool.toBytes(exchangeDeclareArguments);

        Request request = new Request();
        request.setType(0x3);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(exchangeDeclareArguments.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x4
     * @param exchangeName
     * @return
     * @throws IOException
     */
    public boolean exchangeDelete(String exchangeName) throws IOException {
        ExchangeDeleteArguments exchangeDeleteArguments = new ExchangeDeleteArguments();
        exchangeDeleteArguments.setRid(generateRid());
        exchangeDeleteArguments.setChannelId(channelId);
        exchangeDeleteArguments.setExchangeName(exchangeName);
        byte[] payload = BinaryTool.toBytes(exchangeDeleteArguments);

        Request request = new Request();
        request.setType(0x4);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(exchangeDeleteArguments.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x5
     * @param queueName
     * @param durable
     * @param exclusive
     * @param autoDelete
     * @param arguments
     * @return
     * @throws IOException
     */
    public boolean queueDeclare(String queueName, boolean durable, boolean exclusive,
                                boolean autoDelete, Map<String, Object> arguments) throws IOException {
        QueueDeclareArguments queueDeclareArguments = new QueueDeclareArguments();
        queueDeclareArguments.setRid(generateRid());
        queueDeclareArguments.setChannelId(channelId);
        queueDeclareArguments.setQueueName(queueName);
        queueDeclareArguments.setDurable(durable);
        queueDeclareArguments.setExclusive(exclusive);
        queueDeclareArguments.setAutoDelete(autoDelete);
        queueDeclareArguments.setArguments(arguments);
        byte[] payload = BinaryTool.toBytes(queueDeclareArguments);

        Request request = new Request();
        request.setType(0x5);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(queueDeclareArguments.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x6
     * @param queueName
     * @return
     */
    public boolean queueDelete(String queueName) throws IOException {
        QueueDeleteArguments queueDeleteArguments = new QueueDeleteArguments();
        queueDeleteArguments.setRid(generateRid());
        queueDeleteArguments.setChannelId(channelId);
        queueDeleteArguments.setQueueName(queueName);
        byte[] payload = BinaryTool.toBytes(queueDeleteArguments);

        Request request = new Request();
        request.setType(0x6);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(queueDeleteArguments.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x7
     * @param exchangeName
     * @param queueName
     * @param bindingKey
     * @return
     */
    public boolean queueBind(String exchangeName, String queueName, String bindingKey) throws IOException {
        QueueBindArguments queueBindArguments = new QueueBindArguments();
        queueBindArguments.setRid(generateRid());
        queueBindArguments.setChannelId(channelId);
        queueBindArguments.setExchangeName(exchangeName);
        queueBindArguments.setQueueName(queueName);
        queueBindArguments.setBindingKey(bindingKey);
        byte[] payload = BinaryTool.toBytes(queueBindArguments);

        Request request = new Request();
        request.setType(0x7);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(queueBindArguments.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x8
     * @param exchangeName
     * @param queueName
     * @return
     * @throws IOException
     */
    public boolean queueUnBind(String exchangeName, String queueName) throws IOException {
        QueueUnBindArguments queueUnBindArguments = new QueueUnBindArguments();
        queueUnBindArguments.setRid(generateRid());
        queueUnBindArguments.setChannelId(channelId);
        queueUnBindArguments.setExchangeName(exchangeName);
        queueUnBindArguments.setQueueName(queueName);
        byte[] payload = BinaryTool.toBytes(queueUnBindArguments);

        Request request = new Request();
        request.setType(0x8);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(queueUnBindArguments.getRid());
        return basicResponse.isOk();
    }

    /**
     * 0x9
     * @return
     */
    public boolean basicPublish(String exchangeName, String routingKey,
                                BasicProperties basicProperties, byte[] body) throws IOException {
        BasicPublishArguments basicPublishArguments = new BasicPublishArguments();
        basicPublishArguments.setRid(generateRid());
        basicPublishArguments.setChannelId(channelId);
        basicPublishArguments.setExchangeName(exchangeName);
        basicPublishArguments.setRoutingKey(routingKey);
        basicPublishArguments.setBasicProperties(basicProperties);
        basicPublishArguments.setBody(body);
        byte[] payload = BinaryTool.toBytes(basicPublishArguments);

        Request request = new Request();
        request.setType(0x9);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(basicPublishArguments.getRid());
        return basicResponse.isOk();
    }

    public boolean basicConsume(String consumerTag, String queueName, boolean autoAck,
                                Consumer consumer) throws MqException, IOException {
        if(this.consumer != null) {
            throw new MqException("[Channel] 当前 consumer 已存在");
        }
        this.consumer = consumer;
        BasicConsumeArguments basicConsumeArguments = new BasicConsumeArguments();
        basicConsumeArguments.setRid(generateRid());
        basicConsumeArguments.setChannelId(channelId);
        basicConsumeArguments.setConsumerTag(channelId);
        basicConsumeArguments.setQueueName(queueName);
        basicConsumeArguments.setAutoAck(autoAck);
        byte[] payload = BinaryTool.toBytes(basicConsumeArguments);

        Request request = new Request();
        request.setType(0xa);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(basicConsumeArguments.getRid());
        return basicResponse.isOk();
    }

    public boolean basicAck(String queueName, String messageId) throws IOException {
        BasicAckArguments basicAckArguments = new BasicAckArguments();
        basicAckArguments.setRid(generateRid());
        basicAckArguments.setChannelId(channelId);
        basicAckArguments.setQueueName(queueName);
        basicAckArguments.setMessageId(messageId);
        byte[] payload = BinaryTool.toBytes(basicAckArguments);

        Request request = new Request();
        request.setType(0xb);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicResponse basicResponse = waitResponse(basicAckArguments.getRid());
        return basicResponse.isOk();
    }


}

